﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.Diagnostics;
using System.Collections;
using System.Data;
using LinFu.Reflection;


namespace ObjectBakery.Helpers
{
    public class DataSyncHelper
    {
        /// <summary>
        /// Synchronizes a DataRow to an IDictionary
        /// </summary>
        /// <param name="sourceRow"></param>
        /// <param name="targetDictionary"></param>
        public void Sync(DataRow sourceRow, IDictionary targetDictionary)
        {
            DataTable table = sourceRow.Table;
            for (int i = 0; i < table.Columns.Count; i++)
            {
                DataColumn column = table.Columns[i];
                try
                {
                    object sourceValue = sourceRow[column];

                    if (sourceValue != null)
                    {
                        if (sourceValue.GetType().Equals(column.DataType))
                        {
                            //does handle nullable types - see overload for known in advanced 
                            object valueToAssign = null;
                            To(sourceValue, out valueToAssign, sourceValue.GetType());

                            if (valueToAssign != null)
                            {
                                targetDictionary[column.ColumnName] = valueToAssign;
                            }
                        }
                    }
                }
                catch (ApplicationException ex)
                {
                    Debug.WriteLine(ex.Message);
                }

            }
        }

        /// <summary>
        /// Synchronizes an Dictionary to a DataRow
        /// </summary>
        /// <param name="sourceDictionary"></param>
        /// <param name="targetRow"></param>
        public void Sync(IDictionary sourceDictionary, DataRow targetRow)
        {
            DataTable table = targetRow.Table;
            table.Columns.Cast<DataColumn>().ToList().ForEach(column =>
            {
                try
                {
                    object sourceValue = sourceDictionary[column.ColumnName];

                    if (sourceValue != null)
                    {
                        if (sourceValue.GetType().Equals(column.DataType))
                        {
                            //does handle nullable types - see overload for known in advanced 
                            object valueToAssign = null;
                            To(sourceValue, out valueToAssign, sourceValue.GetType());

                            if (valueToAssign != null)
                            {
                                targetRow[column] = valueToAssign;
                            }
                        }
                    }
                }
                catch (ApplicationException ex)
                {
                    Debug.WriteLine(ex.Message);
                }

            });
        }

        /// <summary> 
        /// synchronizes a DataRow to an object's properties 
        /// uses reflection to figure out types to convert objects in the DataRow to when setting object's properties 
        /// </summary> 
        /// <param name="source"></param> 
        /// <param name="target"></param> 
        public void Sync(object source, DataRow targetRow)
        {
            DataTable table = targetRow.Table;
            table.Columns.Cast<DataColumn>().ToList().ForEach(column =>
            {
                try
                {
                    PropertyInfo sourceObjectProperty = source.GetType().GetProperty(column.ColumnName);

                    if (sourceObjectProperty != null)
                    {
                        var type = sourceObjectProperty.PropertyType;
                        if (IsNullable(type))
                            type = UnderlyingTypeOf(type);
                        if (sourceObjectProperty != null && type.Equals(column.DataType))
                        {
                            object sourceObjectValue = sourceObjectProperty.GetValue(source, null);

                            //does handle nullable types - see overload for known in advanced 
                            object valueToAssign = null;
                            To(sourceObjectValue, out valueToAssign, sourceObjectValue.GetType());

                            if (valueToAssign != null)
                            {
                                targetRow[column] = valueToAssign;
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                }

            });
        }


        

        /// <summary> 
        /// synchronizes a DataRow to an object's properties 
        /// uses reflection to figure out types to convert objects in the DataRow to when setting object's properties 
        /// </summary> 
        /// <param name="source"></param> 
        /// <param name="target"></param> 
        public void Sync(DataRow sourceRow, object targetObject)
        {
            DataTable table = sourceRow.Table;
            table.Columns.Cast<DataColumn>().ToList().ForEach(column =>
                {
                    try
                    {
                        PropertyInfo targetObjectProperty = targetObject.GetType().GetProperty(column.ColumnName);

                        if (targetObjectProperty != null)
                        {
                            object sourceObjectValue = sourceRow[column];

                            if (sourceObjectValue != null)
                            {
                                //does handle nullable types - see overload for known in advanced 
                                object valueToAssign = null;
                                To(sourceObjectValue, out valueToAssign, sourceObjectValue.GetType());

                                if (valueToAssign != null)
                                {
                                    targetObjectProperty.SetValue(targetObject, valueToAssign, null);
                                }
                            }
                        }
                    }
                    catch (ApplicationException ex)
                    {
                        Debug.WriteLine(ex.Message);
                    }

                });
        }

        /// <summary> 
        /// synchronizes a Dictionary to an object's properties 
        /// uses reflection to figure out types to convert objects in entry.Value to when setting object's properties 
        /// </summary> 
        /// <param name="source"></param> 
        /// <param name="target"></param> 
        public void Sync(IDictionary sourceDictionary, object targetObject)
        {
            foreach (DictionaryEntry entry in sourceDictionary)
            {
                try
                {
                    PropertyInfo targetObjectProperty = targetObject.GetType().GetProperty(entry.Key.ToString());

                    if (targetObjectProperty != null)
                    {
                        object sourceObjectValue = entry.Value;

                        if (sourceObjectValue != null)
                        {
                            //does handle nullable types - see overload for known in advanced 
                            object valueToAssign = null;
                            To(sourceObjectValue, out valueToAssign, sourceObjectValue.GetType());

                            if (valueToAssign != null)
                            {
                                targetObjectProperty.SetValue(targetObject, valueToAssign, null);
                            }
                        }
                    }
                }
                catch (ApplicationException ex)
                {
                    Debug.WriteLine(ex.Message);
                }
            }
        }

        /// <summary> 
        /// synchronizes a Dictionary to an object's properties 
        /// uses reflection to figure out types to convert objects in entry.Value to when setting object's properties 
        /// </summary> 
        /// <param name="source"></param> 
        /// <param name="target"></param> 
        public void Sync(object sourceObject, IDictionary targetDictionary)
        {
            sourceObject.GetType().GetProperties().ToList().ForEach(sourceObjectProperty =>
            {

                try
                {
                    if (!targetDictionary.Contains(sourceObjectProperty.Name))
                    {
                        targetDictionary.Add(sourceObjectProperty.Name, null);
                    }

                        object sourceObjectValue = sourceObjectProperty.GetValue(sourceObject, null);
                        
                        if (sourceObjectValue != null)
                        {
                            //does handle nullable types - see overload for known in advanced 
                            object valueToAssign = null;
                            To(sourceObjectValue, out valueToAssign, sourceObjectValue.GetType());

                            if (valueToAssign != null)
                            {
                                targetDictionary[sourceObjectProperty.Name] = valueToAssign;
                            }
                        
                    }
                }
                catch (ApplicationException ex)
                {
                    Debug.WriteLine(ex.Message);
                }
            });
        }

        /// <summary> 
        /// synchronizes an object reference's properties' values to another object reference's properties' values 
        /// </summary> 
        /// <param name="source"></param> 
        /// <param name="target"></param> 
        public void Sync(object sourceObject, object targetObject)
        {
            sourceObject.GetType().GetProperties().ToList().ForEach(sourceObjectProperty =>
            {
                try
                {
                    PropertyInfo targetObjectProperty = targetObject.GetType().GetProperty(sourceObjectProperty.Name);

                    if (targetObjectProperty != null && targetObjectProperty.PropertyType.Equals(sourceObjectProperty.PropertyType))
                    {
                        object sourceObjectValue = sourceObjectProperty.GetValue(sourceObject, null);

                        if (sourceObjectValue != null)
                        {
                            //does handle nullable types - see overload for known in advanced 
                            object valueToAssign = null;
                            To(sourceObjectValue, out valueToAssign, sourceObjectValue.GetType());

                            if (valueToAssign != null)
                            {
                                targetObjectProperty.SetValue(targetObject, valueToAssign, null);
                            }
                        }
                    }
                }
                catch (ApplicationException ex)
                {
                    Debug.WriteLine(ex.Message);
                }
            });
        }

        /// <summary> 
        /// copies a value to another 
        /// </summary> 
        /// <param name="srcValue"></param> 
        /// <param name="targetValue"></param> 
        /// <param name="t"></param> 
        public void To(object srcValue, out object targetValue, Type t)
        {
            targetValue = null;
            if (srcValue == DBNull.Value) return;

            if (IsNullable(t))
            {
                if (srcValue == null)
                {
                    return;
                }
                targetValue = UnderlyingTypeOf(t);
            }

            targetValue = Convert.ChangeType(srcValue, t);
        }

        /// <summary> 
        /// generic version of To 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="value"></param> 
        /// <param name="defaultValue"></param> 
        /// <returns></returns> 
        public T To<T>(object value, T defaultValue)
        {
            if (value == DBNull.Value) return defaultValue;
            Type t = typeof(T);
            if (IsNullable(t))
            {
                if (value == null) return default(T);
                t = UnderlyingTypeOf(t);
            }

            return (T)Convert.ChangeType(value, t);
        }

        /// <summary> 
        /// figures out if the Type is Nullable"/> 
        /// </summary> 
        /// <param name="t"></param> 
        /// <returns></returns> 
        private bool IsNullable(Type t)
        {
            if (!t.IsGenericType) return false;
            Type g = t.GetGenericTypeDefinition();
            return (g.Equals(typeof(Nullable<>)));
        }

        /// <summary> 
        /// gets the underlying Type 
        /// </summary> 
        /// <param name="t"></param> 
        /// <returns></returns> 
        private Type UnderlyingTypeOf(Type t)
        {
            return t.GetGenericArguments()[0];
        }


        public void EnsureStructure(object sourceObject, DataRow targetRow)
        {
            DataTable table = targetRow.Table;
            sourceObject.GetType().GetProperties().ToList().ForEach(sourceObjectProperty =>
                                                                        {
                                                                            var column = table.Columns.Cast<DataColumn>()
                                                                                .ToList().
                                                                                Find(col =>
                                                                                     col.ColumnName.ToUpper() ==
                                                                                     sourceObjectProperty.Name.ToUpper());
                                                                            if (column == null)
                                                                            {
                                                                                var type = sourceObjectProperty.
                                                                                    PropertyType;
                                                                               
                                                                                if (!IsNullable(type))
                                                                                    table.Columns.Add(
                                                                                        sourceObjectProperty.Name,type
                                                                                        ); 
                                                                                else
                                                                                    table.Columns.Add(
                                                                                        sourceObjectProperty.Name,
                                                                                        UnderlyingTypeOf(type));
                                                                                
                                                                            }
                                                                        });
        }
    }
}
